#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <math.h>

#define DATA_LEN 56
int sendnum=0;//发送数据包编号
int recvnum=0;//接受数据包的编号
char sendbuf[1024];//发送数据包
char recvbuf[1024];//接受数据包

char *ip=NULL;
int n_send=0;
int n_recv=0;

int flag=0;//如果是域名的话，返回的是（IP）IP，反之则直接返回的是IP
int sfd=0;//在进程退出的时候要关闭文件描述符，所以在不用的时候就要进行关闭。

float min=0.0;
float arg=0.0;
float max=0.0;
float mdev=0.0;


struct timeval Lasttime;
struct timeval Firsttime;


//计算两个时间差（秒数）系统函数difftime
double diftime(struct timeval *end,struct timeval *bg)
{
    double ret=(end->tv_sec - bg->tv_sec)*1000.0+(end->tv_usec - bg->tv_usec)/1000.0;
    return ret;
}
void Handler(int signo)
{
	float a=n_send;
	float b=n_recv;
    
	arg/=n_recv;
	//算出mdev，在这里需要注意的是利用平方差公式计算
	double tmp=mdev/n_recv-arg*arg;
	mdev=sqrt(tmp);

	printf("\n --- %s  ping statistics ---\n",ip);
	printf("%d packets transmitted, %d received, %d%% packet loss, time %dms\n",\
			n_send,
			n_recv,
			(int)(((a-b)/a)*100),
			(int)(diftime(&Lasttime,&Firsttime)));
	printf("rtt min/avrg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n",\
		  							min,arg,max,mdev);
	close(sfd);

	exit(1);
}
//addr：需要计算校验和的数据起始地址
//len：数据大小。单位字节
unsigned short chksum(unsigned short *addr,int len)
{
    unsigned int ret=0;
    while(len>1)
    {
	ret+=*addr++;
	len-=2;
    }

    if(len==-1)
    {
	ret+=*(unsigned char*)addr;
    }

    ret=(ret>>16)+(ret&0xffff);
    ret+=(ret>>16);

    return (unsigned short)~ret;
}
#if 0
struct icmp
{
  u_int8_t  icmp_type;	/* type of message, see below */
  u_int8_t  icmp_code;	/* type sub code */
  u_int16_t icmp_cksum;	/* ones complement checksum of struct */
  union
  {
    u_char ih_pptr;		/* ICMP_PARAMPROB */
    struct in_addr ih_gwaddr;	/* gateway address */
    struct ih_idseq		/* echo datagram */
    {
      u_int16_t icd_id;
      u_int16_t icd_seq;
    } ih_idseq;
    u_int32_t ih_void;

    /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
    struct ih_pmtu
    {
      u_int16_t ipm_void;
      u_int16_t ipm_nextmtu;
    } ih_pmtu;

    struct ih_rtradv
    {
      u_int8_t irt_num_addrs;
      u_int8_t irt_wpa;
      u_int16_t irt_lifetime;
    } ih_rtradv;
  } icmp_hun;
#define	icmp_pptr	icmp_hun.ih_pptr
#define	icmp_gwaddr	icmp_hun.ih_gwaddr
#define	icmp_id		icmp_hun.ih_idseq.icd_id
#define	icmp_seq	icmp_hun.ih_idseq.icd_seq
#define	icmp_void	icmp_hun.ih_void
#define	icmp_pmvoid	icmp_hun.ih_pmtu.ipm_void
#define	icmp_nextmtu	icmp_hun.ih_pmtu.ipm_nextmtu
#define	icmp_num_addrs	icmp_hun.ih_rtradv.irt_num_addrs
#define	icmp_wpa	icmp_hun.ih_rtradv.irt_wpa
#define	icmp_lifetime	icmp_hun.ih_rtradv.irt_lifetime
 

#endif

//组包
//num：ICMP报文序号
int pack(int num,pid_t pid)
{
    int len=DATA_LEN+8;
    struct icmp* picmp=(struct icmp*)sendbuf;
    picmp->icmp_type =ICMP_ECHO;
    picmp->icmp_code =0;
    picmp->icmp_cksum=0;
    picmp->icmp_id=pid;
    picmp->icmp_seq=htons(num);

    gettimeofday((struct timeval*)(picmp->icmp_data),NULL);
    picmp->icmp_cksum=chksum((unsigned short*)sendbuf,len);
	if(num==1)
	{	//得到第一次发送的时候的时间戳。
		Firsttime=*((struct timeval*)(picmp->icmp_data));
	}
    return len;
}
//发送数据包
//sfd：套接字描述夫
//pid：组ICMP报文标识符字段
//addr：发送的目标主机
void send_packet(int sfd,pid_t pid,struct sockaddr_in addr)
{
   n_send++;
   sendnum++;
   int r= pack(sendnum,pid);//组包
    sendto(sfd,sendbuf,r,0,(struct sockaddr*)&addr,sizeof(addr));
}

void unpack(int num,pid_t pid,struct sockaddr_in from)
{
    struct timeval end;
    gettimeofday(&end,NULL);
    struct ip *pip=(struct ip*)recvbuf;
    struct icmp *picmp=(struct icmp*)(recvbuf+(pip->ip_hl<<2));
    float time=diftime(&end,(struct timeval*)picmp->icmp_data);
    	
    printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.1fms\n",
	    DATA_LEN+8,inet_ntoa(from.sin_addr),
	    ntohs(picmp->icmp_seq),pip->ip_ttl,time);
	if(time<min || min==0)
	{
		min=time;
	}
	if(time>max)
	{
		max=time;
	}
    arg+=time;
	mdev+=(time*time);
}
//接受数据包
void recv_packet(int sfd,pid_t pid)
{
	signal(SIGALRM,Handler);
	alarm(5);


    struct sockaddr_in from;
    socklen_t len=sizeof(from);
    memset(recvbuf,0x00,sizeof(recvbuf));
    recvnum++;
    recvfrom(sfd,recvbuf,1024,0,(struct sockaddr*)&from,&len);
	struct timeval tmp;
	gettimeofday(&tmp,NULL);
	Lasttime=tmp;

	n_recv++;
    unpack(recvnum,pid,from);
}


int main(int argc,char *argv[])
{
    if(argc!=2)
    {
	fprintf(stderr,"usage :./ping ip/域名\n");
	exit(1);
    }
	
    struct sockaddr_in addr;
    struct hostent *phost;

    addr.sin_addr.s_addr=inet_addr(argv[1]);
    if(addr.sin_addr.s_addr==INADDR_NONE)
    {
		phost=gethostbyname(argv[1]);
		if(phost==NULL)
		{
	    	perror("gethostbyname");
	    	exit(1);
		}
		memcpy((void*)&addr.sin_addr,(void*)phost->h_addr,phost->h_length);
    
    	printf("PING %s(%s) %d(84) bytes of data.\n",
	    	phost->h_name,
	    	inet_ntoa(addr.sin_addr),
	    	DATA_LEN);
	}
	else
	{
		
    	printf("PING %s(%s) %d(84) bytes of data.\n",
	    	argv[1],
	    	inet_ntoa(addr.sin_addr),
	    	DATA_LEN);
	}
	ip=argv[1];
	signal(SIGINT,Handler);
	
    addr.sin_family=AF_INET;
	
    int sfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
    if(sfd==-1)
    {
		perror("socket"),exit(1);
	}

	pid_t pid=getpid();
	while(1)
	{
	    send_packet(sfd,pid,addr);
	    recv_packet(sfd,pid);
	    sleep(1);
	}
}
